﻿using System;
using System.Collections.Generic;
using System.Linq;

using System.IO;

using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using Modbus.Device;
using Modbus.Utility;
using System.Diagnostics;



namespace modBusTest
{
    public class mapFields
    {
        public float result_f;
        public ushort result_w;
        public string paramName;
        public int register;
        public enum paramType { float_t, word_t };
        public enum regType { holding, input };
        public paramType type;
        public regType registerType;
        public string value {
            get{
                if(type == paramType.float_t){
                    return result_f.ToString();
                } else {
                    return result_w.ToString();
                }
            }
            set
            {
                if (type == paramType.float_t)
                {
                    result_f=float.Parse(value);
                }
                else
                {
                    result_w = ushort.Parse(value); 
                }
            }
        }
    }
    
    public class modbusmap
    {
        public static int plcOffset = 1;
        

        //private List<mapFields> storage = new List<mapFields>();
        public List<mapFields> storage;
        
        private optimizeRange optInputReg = new optimizeRange();
        private optimizeRange optHoldReg = new optimizeRange();
        private ushort[] holdingRegisters = new ushort[10001];
        private ushort[] inputRegisters = new ushort[10001];

        /// <summary>
        /// Handle to setup up modbus port
        /// </summary>
        public IModbusSerialMaster master { set; get; }
        /// <summary>
        /// slave id with the device you are trying to communicate with
        /// </summary>
        public byte slaveID { set; get; }
        /// <summary>
        /// Read data from file into internal database
        /// </summary>
        /// <param name="fileName">Name of file to read</param>
        public void readData(string fileName){
            storage = new List<mapFields>();
        
            using (StreamReader sr = new StreamReader(fileName))
            {
                string stringToParse;
                while((stringToParse = sr.ReadLine())!=null){
                    string[] words = stringToParse.Split(',');

                    mapFields tempFile = new mapFields();
                    
                    //parse parameter name
                    tempFile.paramName = words[0];
                    
                    //parse the Address
                    tempFile.register = Int32.Parse(words[1]);

                    tempFile = parseRegisterType(tempFile);

                    tempFile = parseRegisterDataType(words, tempFile);

                    storage.Add(tempFile);
                }
            }
            //populate the opitimize structure
            processHoldingRegister();
            processInputRegister();
            
            //solve for optimal number of modbus command
            optInputReg.solve();
            optHoldReg.solve();
        
        }
        

        private  mapFields parseRegisterDataType(string[] words, mapFields tempFile)
        {
            if (words[2] == "f")
            {
                tempFile.type = mapFields.paramType.float_t;
            }
            else if (words[2] == "w")
            {
                tempFile.type = mapFields.paramType.word_t;
            }
            return tempFile;
        }

        private  mapFields parseRegisterType(mapFields tempFile)
        {
            if (tempFile.register >= 40000)
            {
                tempFile.registerType = mapFields.regType.holding;
            }
            else
            {
                tempFile.registerType = mapFields.regType.input;
            }
            return tempFile;
        }

        public mapFields findParam(string paramName)
        {
            for (int i = 0; i < storage.Count; i++)
            {
                if(storage[i].paramName == paramName){
                    return storage[i];
                }
            }

            throw (new Exception("Param Does Not Exist"));
        }
        /// <summary>
        /// exposes internal data structure for the modbus commands
        /// </summary>
        /// <returns>returns that internal structure</returns>
        public List <mapFields> getList()
        {
            parseResults();
            return storage;
        }

        private void processInputRegister(){
            populateOptimize(30000, optInputReg, mapFields.regType.input);
        
        }

        private void processHoldingRegister()
        {
            populateOptimize(40000, optHoldReg, mapFields.regType.holding);
        }
        /// <summary>
        /// Function will populate optimize range class 
        /// </summary>
        /// <param name="offset">offset is 30000 or 40000 depending on input or
        /// holding register</param>
        /// <param name="regType">passes in the optimizerange class for ether
        /// input or holding register</param>
        private void populateOptimize(int offset, optimizeRange regType, mapFields.regType type)
        {
            foreach (mapFields item in storage)
            {
                if (item.registerType == type)
                {
                    if (item.type == mapFields.paramType.word_t)
                    {
                        regType.addRegister((ushort)(item.register - offset ));
                    }
                    else if (item.type == mapFields.paramType.float_t)
                    {
                        regType.addRegister((ushort)(item.register - offset));
                        regType.addRegister((ushort)(item.register - offset + 1));
                    }
                }
            }
        }
        /// <summary>
        /// need serial modbus port created and initialized and slave id set
        /// </summary>
        /// <returns>return the amount of time taken for command in ms</returns>
        public long getData()
        {
            if (master == null || slaveID == 0)
            {
                throw new Exception("Set port handle and slave ID");
            }
            


            master.Transport.ReadTimeout = 100;

            // time how long this operation takes 
            Stopwatch timer = new Stopwatch();
            timer.Reset();
            timer.Start();

            getInputRegisterValues();
            getHoldingRegisterValues();
            timer.Stop();

            return timer.ElapsedMilliseconds;
        }
        /// <summary>
        /// function will fetch input register values
        /// </summary>
        private void getInputRegisterValues()
        {
            foreach (pair item in optInputReg.getSolution())
            {
                ushort[] input = master.ReadInputRegisters(slaveID, (ushort)(item.baseRegister - plcOffset), (ushort)item.range);

                for (int i = 0; i < input.Length; i++)
                {
                    inputRegisters[item.baseRegister+i] = input[i];
                }

            }
        }
        

        /// <summary>
        /// function will go out and get holding register values
        /// </summary>
        private long  getHoldingRegisterValues()
        {
            Stopwatch test = new Stopwatch();
            test.Reset();
            test.Start();
            foreach (pair item in optHoldReg.getSolution())
            {
                
                ushort[] input = master.ReadHoldingRegisters(slaveID, (ushort)(item.baseRegister - plcOffset), (ushort)item.range);

                for (int i = 0; i < input.Length; i++)
                {
                    holdingRegisters[item.baseRegister+i] = input[i];
                }
                
                

            }
            test.Stop();
            return test.ElapsedMilliseconds;
        }

        private void parseResults()
        {
            for (int i=0; i<storage.Count; i++)
            {
                if (storage[i].registerType == mapFields.regType.holding && storage[i].type == mapFields.paramType.float_t)
                {
                    //storage[i].register = 12123;
                    storage[i].result_f = ModbusUtility.GetSingle(holdingRegisters[storage[i].register-40000],holdingRegisters[storage[i].register-40000+1]);
                }
                else if (storage[i].registerType == mapFields.regType.input && storage[i].type == mapFields.paramType.float_t)
                {
                    storage[i].result_f = ModbusUtility.GetSingle(inputRegisters[storage[i].register - 30000], inputRegisters[storage[i].register - 30000 + 1]);
                } else if (storage[i].registerType == mapFields.regType.holding && storage[i].type == mapFields.paramType.word_t)
                {
                    //storage[i].register = 12123;
                    storage[i].result_w =holdingRegisters[storage[i].register-40000];
                }
                else if (storage[i].registerType == mapFields.regType.input && storage[i].type == mapFields.paramType.word_t)
                {
                    //storage[i].register = 12123;
                    storage[i].result_w = inputRegisters[storage[i].register - 30000];
                }
            }
        }
    }
}
